module sdram_controller(
    input         clk,              //SDRAM控制器时钟，100MHz
    input         rst_n,            //系统复位信号，低电平有效

    // FPGA与SDRAM硬件接口
    output            sdram_cke,        // SDRAM 时钟有效信号
    output            sdram_cs0_n,       // SDRAM 片选信号
    output            sdram_cs1_n,       // SDRAM 片选信号
    output            sdram_ras_n,      // SDRAM 行地址选通脉冲
    output            sdram_cas_n,      // SDRAM 列地址选通脉冲
    output            sdram_we_n,       // SDRAM 写允许位
    output reg [ 1:0] sdram_ba,         // SDRAM L-Bank地址线
    output reg [12:0] sdram_addr,       // SDRAM 地址总线
    inout  [15:0]     sdram_data,        // SDRAM 数据总线
    output  [ 1:0]    sdram_dqm,
    
    //TODO 读写地址合并 burst 合并
    
    output        idle,
    //SDRAM 控制器写端口  
    input  [24:0] sdram_rw_addr,    //SDRAM写操作的地址 25bit = 32M word = 64Mbyte
    output        sdram_rw_ack,
    input         sdram_wr_req,     //写SDRAM请求信号
    //output        sdram_wr_ack,     //写SDRAM响应信号
    input  [ 9:0] sdram_wr_burst,   //写sdram时数据突发长度
    input  [15:0] sdram_din,        //写入SDRAM的数据
    input  [ 1:0] sdram_mask,
    
    //SDRAM 控制器读端口  
    input         sdram_rd_req,     //读SDRAM请求信号
    //output        sdram_rd_ack,     //读SDRAM响应信号
    input  [ 9:0] sdram_rd_burst,   //读sdram时数据突发长度
    output [15:0] sdram_dout,       //从SDRAM读出的数据
    
    input block_auto_refresh
    
    );

// SDRAM 工作过程各个状态
localparam W_INOP    = 4'd0; //等待上电200us稳定期结束
localparam W_IMRS    = 4'd1; //模式寄存器设置
localparam W_IDLE    = 4'd2; //空闲
localparam W_ACTIVE  = 4'd3; //行有效
localparam W_TRCD    = 4'd4; //行有效等待
localparam W_READ    = 4'd5; //读操作
localparam W_CL      = 4'd6; //潜伏期
localparam W_RD      = 4'd7; //读数据
localparam W_WRITE   = 4'd8; //写操作
localparam W_WD      = 4'd9; //写数据
localparam W_TWR     = 4'd10; //写回
localparam W_PRE     = 4'd11; //预充电
localparam W_TRP     = 4'd12;//预充电等待
localparam W_AR      = 4'd13;//自动刷新
localparam W_TRFC    = 4'd14;//自动刷新等待


localparam TRP_CLK  = 10'd4; //预充电有效周期
localparam TRC_CLK  = 10'd6; //自动刷新周期
localparam TRSC_CLK = 10'd6; //模式寄存器设置时钟周期
localparam TRCD_CLK = 10'd2; //行选通周期
localparam TCL_CLK  = 10'd3; //列潜伏期
localparam TWR_CLK  = 10'd2; //写入校正//写回周期结束

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// SDRAM 状态控制模块


//SDRAM 自动刷新应答信号
wire sdram_ref_ack = (work_state == W_AR);

assign sdram_rw_ack = (work_state == W_ACTIVE) & (sdram_rd_wr==WRITE) | (work_state == W_RD);
//晚一个周期
//assign sdram_rw_ack = (work_state == W_TRCD) & (sdram_rd_wr==WRITE) | (work_state == W_RD) & cnt_clk[0];


wire done_200us; //上电后200us输入稳定期结束标志位
//SDRAM上电后200us稳定期结束后,将标志信号拉高
assign done_200us = (cnt_200us[14]);

reg [14:0] cnt_200us;//SDRAM 上电稳定期200us计数器
//上电后计时200us,等待SDRAM状态稳定
always @ (posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    cnt_200us <= 15'd0;
  end else begin
    if(cnt_200us[14]==0) begin
      cnt_200us <= cnt_200us + 1'b1;
    end else begin
      cnt_200us <= cnt_200us;
    end
  end
end

localparam REFRESH_TOP  = 10'd780; //写入校正//写回周期结束

//替换成10位 9:0
reg [9:0] cnt_refresh;//刷新计数寄存器
//刷新计数器循环计数7812ns (60ms内完成全部8192行刷新操作)
always @ (posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    cnt_refresh <= 10'd0;
  end else begin
    if(cnt_refresh == REFRESH_TOP) begin // 64ms/8192 =7812ns
      cnt_refresh <= 10'd0;
    end else begin
      cnt_refresh <= cnt_refresh + 1'b1;
    end
  end
end


reg sdram_ref_req; //SDRAM 自动刷新请求信号
//SDRAM 刷新请求
always @ (posedge clk or negedge rst_n)begin
  if(!rst_n) begin
    sdram_ref_req <= 1'b0;
  end else begin
    if(cnt_refresh == REFRESH_TOP && !block_auto_refresh) begin
      sdram_ref_req <= 1'b1;//刷新计数器计时达7812ns时产生刷新请求
    end else if(sdram_ref_ack) begin
      sdram_ref_req <= 1'b0;//收到刷新请求响应信号后取消刷新请求 
    end
  end
end

//    output reg [9:0] cnt_clk,	        //时钟计数器
reg [9:0] cnt_clk;                 // 延时计数器
//延时计数器对时钟计数
always @ (posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    cnt_clk <= 10'd0;
  end else begin
    if(!cnt_rst_n) begin                //在cnt_rst_n为低电平时延时计数器清零
      cnt_clk <= 10'd0;
    end else begin
      cnt_clk <= cnt_clk + 1'b1;
    end
  end
end


reg cnt_rst_n; //延时计数器复位信号，低有效
//计数器控制逻辑
always @ (*) begin
  case (work_state)
    W_INOP: //延时计数器清零(cnt_rst_n低电平复位)
      cnt_rst_n <= 1'b0;
    W_IMRS: //模式寄存器设置：延时计数器启动
      cnt_rst_n <= 1'b1;//等待模式寄存器设置延时计数结束后，清零计数器
    W_IDLE://行有效：延时计数器启动
      cnt_rst_n <= 1'b0;
    W_ACTIVE: //行有效延时计数结束后，清零计数器
      cnt_rst_n <= 1'b1;
    W_TRCD: //潜伏期延时计数结束后，清零计数器
      cnt_rst_n <= (cnt_clk == TRCD_CLK-1)   ? 1'b0 : 1'b1;
    W_CL: //读数据延时计数结束后，清零计数器
      cnt_rst_n <= (cnt_clk == TCL_CLK-1)    ? 1'b0 : 1'b1;
    W_RD: //写数据延时计数结束后，清零计数器
      cnt_rst_n <= (cnt_clk == sdram_rd_burst+2)  ? 1'b0 : 1'b1;
    W_WD: //写回周期延时计数结束后，清零计数器
      cnt_rst_n <= (cnt_clk == sdram_wr_burst-1) ? 1'b0 : 1'b1;
    W_TWR: //预充电等待延时计数结束后，清零计数器
      cnt_rst_n <= (cnt_clk == TWR_CLK )    ? 1'b0 : 1'b1;
    W_TRP: //自动刷新等待延时计数结束后，清零计数器
      cnt_rst_n <= (cnt_clk == TRP_CLK) ? 1'b0 : 1'b1;
    W_TRFC:
      cnt_rst_n <= (cnt_clk == TRC_CLK)   ? 1'b0 : 1'b1;
    default:
      cnt_rst_n <= 1'b0;
  endcase
end


reg [3:0] work_state;              // SDRAM工作状态
reg       sdram_rd_wr;             // SDRAM读/写控制信号,低电平为写，高电平为读
localparam READ   = 1'd1;//高电平为读
localparam WRITE  = 1'd0;//低电平为写


assign idle = work_state == W_IDLE;


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// SDRAM 状态控制模块 end


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// SDRAM 命令控制模块

//reg define
reg  [ 5:0] sdram_cmd_r;                //SDRAM操作指令

//SDRAM控制信号命令
localparam CMD_INIT    = 6'b110111; // INITIATE
localparam CMD_LMR     = 6'b001000; // LOAD MODE REGISTER
localparam CMD_A_REF   = 6'b001001; // AOTO REFRESH
localparam CMD_PRGE    = 6'b101010; // PRECHARGE
localparam CMD_ACTIVE  = 6'b101011; // ACTIVE COMMAND
localparam CMD_WRITE   = 6'b101100; // WRITE_COMMAND
localparam CMD_READ    = 6'b101101; // READ_COMMAND
localparam CMD_B_STOP  = 6'b101110; // BURST STOP
localparam CMD_NOPINI  = 6'b001111; // NOP COMMAND
localparam CMD_NOP     = 6'b101111; // NOP COMMAND
//localparam CMD_PRGEINI = 6'b001010; // PRECHARGE

//SDRAM 控制信号线赋值
assign {sdram_cke,sdram_ras_n,sdram_cas_n,sdram_we_n} = sdram_cmd_r[3:0];

//SDRAM 读/写地址总线控制

//32M cross
//wire [23:0] sys_addr = {sdram_rw_addr[23:0]};
//wire cs = sdram_rw_addr[24];

//1k cross
wire [23:0] sys_addr = {sdram_rw_addr[24:10],sdram_rw_addr[8:0]};
wire cs = sdram_rw_addr[9];

//2k cross
//wire [23:0] sys_addr = {sdram_rw_addr[24:11],sdram_rw_addr[9:0]};
//wire cs = sdram_rw_addr[10];

//4k cross
//wire [23:0] sys_addr = {sdram_rw_addr[24:12],sdram_rw_addr[10:0]};
//wire cs = sdram_rw_addr[11];

assign sdram_cs0_n = sdram_cmd_r[5:4] == 2'b11 ? 1'b1 : (sdram_cmd_r[5:4] == 2'b00 ? 1'b0 :  cs);
assign sdram_cs1_n = sdram_cmd_r[5:4] == 2'b11 ? 1'b1 : (sdram_cmd_r[5:4] == 2'b00 ? 1'b0 : !cs);

//1chip
//wire [23:0] sys_addr = {sdram_rw_addr[23:0]};
//assign sdram_cs0_n = sdram_cmd_r[4];
//assign sdram_cs1_n = 1'b1;

//SDRAM 操作指令控制
always @ (posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    work_state <= W_INOP;          //空闲状态
    sdram_cmd_r <= CMD_INIT;
    sdram_ba    <= 2'b11;
    sdram_addr  <= 13'h1fff;
  end else begin
    case(work_state)    //以下工作状态不执行任何指令
    W_INOP: begin //上电复位后200us结束则进入下一状态
      work_state <= done_200us  ? W_IMRS : W_INOP;
      sdram_cmd_r <= CMD_NOPINI;
      sdram_ba    <= 2'b11;
      sdram_addr  <= 13'h1fff;    
    end
    W_IMRS: begin               //模式寄存器设置指令
      work_state <= W_IDLE;
      sdram_cmd_r <= CMD_LMR;
      sdram_ba    <= 2'b00;
      sdram_addr  <= {    //利用地址线设置模式寄存器,可根据实际需要进行修改
          3'b000,         //预留
          1'b0,           //读写方式 A9=0，突发读&突发写
          2'b00,          //默认，{A8,A7}=00
          3'b011,         //CAS潜伏期设置，这里设置为3，{A6,A5,A4}=011
          1'b0,           //突发传输方式，这里设置为顺序，A3=0
          3'b111          //突发长度，     页突发3'b111，{A2,A1,A0}=011    ***********************************************************************************************
      };
    end
    W_IDLE: begin//定时自动刷新请求，跳转到自动刷新状态
      sdram_cmd_r <= CMD_NOP;
      sdram_ba    <= 2'b11;
      sdram_addr  <= 13'h1fff;
      if         (sdram_ref_req) begin
        work_state <= W_AR;
        sdram_rd_wr <= READ;
      end else if(sdram_wr_req) begin
        work_state <= W_ACTIVE;
        sdram_rd_wr <= WRITE;
      end else if(sdram_rd_req) begin
        work_state <= W_ACTIVE;
        sdram_rd_wr <= READ;
      end else begin 
        work_state <= W_IDLE;
        sdram_rd_wr <= READ;
      end
    end
    W_ACTIVE: begin//行有效，跳转到行有效等待状态
      work_state <= W_TRCD;
      sdram_cmd_r <= CMD_ACTIVE;
      sdram_ba    <= sys_addr[23:22];
      sdram_addr  <= sys_addr[21:9];
    end
    W_TRCD: begin
      sdram_cmd_r <= CMD_NOP;
      sdram_ba    <= 2'b11;
      sdram_addr  <= 13'h1fff;
      if(cnt_clk == TRCD_CLK-1) begin// 2 clock 行有效等待结束，判断当前是读还是写
        if(sdram_rd_wr == READ) begin
          work_state <= W_READ;//读：进入读操作状态
        end else begin          
          work_state <= W_WRITE;//写：进入写操作状态
        end
      end else begin
        work_state <= W_TRCD;
      end
    end
    
    W_READ: begin//读操作，跳转到潜伏期
      work_state <= W_CL;
      sdram_cmd_r <= CMD_READ;
      sdram_ba    <= sys_addr[23:22];
      sdram_addr  <= {4'b0000,sys_addr[8:0]};//sdram_addr  <= 13'b0100 0000 0000; 高电平 允许自动预充电
    end
    W_CL: begin// 3 clock 潜伏期：等待潜伏期结束，跳转到读数据状态
      work_state <= (cnt_clk == TCL_CLK-1) ? W_RD:W_CL;
      sdram_cmd_r <= CMD_NOP;
      sdram_ba    <= 2'b11;
      sdram_addr  <= 13'h1fff;
    end
    W_RD: begin//读数据：等待读数据结束，跳转到预充电状态
      if(cnt_clk == sdram_rd_burst+2) begin //突发传输终止指令
        work_state <= W_PRE;
        sdram_cmd_r <= CMD_B_STOP;
        sdram_ba    <= 2'b11;
        sdram_addr  <= 13'h1fff;
      end else begin
        work_state <= W_RD;
        sdram_cmd_r <= CMD_NOP;
        sdram_ba    <= 2'b11;
        sdram_addr  <= 13'h1fff;
      end
    end
    
    W_WRITE: begin //写操作指令
      work_state  <= W_WD;
      sdram_cmd_r <= CMD_WRITE;
      sdram_ba    <= sys_addr[23:22];
      sdram_addr  <= {4'b0000,sys_addr[8:0]};//sdram_addr  <= 13'h0400; 高电平 允许自动预充电
    end
    W_WD: begin    //突发传输终止指令  //写数据：等待写数据结束，跳转到写回周期状态
      if(cnt_clk == sdram_wr_burst-1) begin
        work_state  <= W_TWR;
        sdram_cmd_r <= CMD_B_STOP;
        sdram_ba    <= 2'b11;
        sdram_addr  <= 13'h1fff;
      end else begin
        work_state  <= W_WD;
        sdram_cmd_r <= CMD_NOP;
        sdram_ba    <= 2'b11;
        sdram_addr  <= 13'h1fff;
      end
    end
    W_TWR: begin//写回周期：写回周期结束，跳转到预充电状态
      work_state <= (cnt_clk == TWR_CLK ) ? W_PRE:W_TWR;
      sdram_cmd_r <= CMD_NOP;
      sdram_ba    <= 2'b11;
      sdram_addr  <= 13'h1fff;
    end
    
    W_PRE:begin    // 预充电指令  //预充电：跳转到预充电等待状态
      work_state <= W_TRP;
      sdram_cmd_r <= CMD_PRGE;
      sdram_ba    <= sys_addr[23:22];
      sdram_addr  <= 13'h0000;//sdram_addr  <= 13'h0400; 高电平 允许自动预充电
    end
    W_TRP: begin// 4 clock 预充电等待：预充电等待结束，进入空闲状态
      work_state <= (cnt_clk == TRP_CLK) ? W_IDLE:W_TRP;
      sdram_cmd_r <= CMD_NOP;
      sdram_ba    <= 2'b11;
      sdram_addr  <= 13'h1fff;
    end
    
    W_AR: begin    //自动刷新指令 //自动刷新操作，跳转到自动刷新等待
      work_state <= W_TRFC;
      sdram_cmd_r <= CMD_A_REF;
      sdram_ba    <= 2'b11;
      sdram_addr  <= 13'h1fff;
    end
    W_TRFC: begin //自动刷新等待：自动刷新等待结束，进入空闲状态
      work_state <= (cnt_clk == TRC_CLK) ? W_IDLE:W_TRFC;
      sdram_cmd_r <= CMD_NOP;
      sdram_ba    <= 2'b11;
      sdram_addr  <= 13'h1fff;
    end
    default: begin
      work_state <= W_IDLE;
      sdram_cmd_r <= CMD_NOP;
      sdram_ba    <= 2'b11;
      sdram_addr  <= 13'h1fff;
    end
    endcase

  end
end


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// SDRAM 命令控制模块 end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// SDRAM 数据读写模块

//reg define
reg        sdram_out_en;                //SDRAM数据总线输出使能
reg [15:0] sdram_din_r;                 //寄存写入SDRAM中的数据
reg  [1:0] sdram_mask_r;
reg [15:0] sdram_dout_r;                //寄存从SDRAM中读取的数据


//SDRAM 双向数据线作为输入时保持高阻态
assign sdram_data = sdram_out_en ? sdram_din_r : 16'hzzzz;
assign sdram_dqm = sdram_out_en ? sdram_mask_r : 2'b00;

//输出SDRAM中读取的数据
assign sdram_dout = sdram_dout_r;

//SDRAM 数据总线输出使能
always @ (posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    sdram_out_en <= 1'b0;
  end else if((work_state == W_WRITE) | (work_state == W_WD)) 
    sdram_out_en <= 1'b1; //向SDRAM中写数据时,输出使能拉高
  else 
    sdram_out_en <= 1'b0;
end

//将待写入数据送到SDRAM数据总线上
always @ (posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    sdram_din_r <= 16'd0;
    sdram_mask_r <= 0;
  end else if((work_state == W_WRITE) | (work_state == W_WD))begin
    sdram_din_r <= sdram_din;   //寄存写入SDRAM中的数据
    sdram_mask_r <= sdram_mask;
  end
end

//读数据时,寄存SDRAM数据线上的数据
always @ (posedge clk or negedge rst_n) begin
  if(!rst_n) 
    sdram_dout_r <= 16'd0;
  else if(work_state == W_RD) 
    sdram_dout_r <= sdram_data;     //寄存从SDRAM中读取的数据
end
    
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// SDRAM 数据读写模块

endmodule 